package org.biogroovy.io.eutils

import groovy.util.logging.Slf4j
import groovy.util.slurpersupport.GPathResult
import groovy.util.slurpersupport.NodeChild
import groovy.util.slurpersupport.NodeChildren

import java.text.ParseException

import org.biogroovy.eutils.EUtilsURLFactory
import org.biogroovy.io.AbsXmlSlurper;
import org.biogroovy.io.IFetcher;
import org.biogroovy.io.NodeType;
import org.biogroovy.models.Article
import org.biogroovy.models.Author
import org.biogroovy.models.Journal
import org.biogroovy.models.MeshHeading

@Slf4j
class PubMedSlurper extends AbsXmlSlurper<Article>{
	
	static final String DATABASE_NAME = "pubmed";
	
	static final Map<String, String> GPATH_MAP = [
		pubmedId: 'MedlineCitation.PMID',
		doi: 'PubmedData.ArticleIdList.ArticleId.find{it."@IdType" == "doi"}',
		dateCreated:'MedlineCitation.DateCreated',
		dateRevised:'MedlineCitation.DateRevised',
		dateCompleted:'MedlineCitation.DateCompleted',
		title:'MedlineCitation.Article.ArticleTitle',
		abs: 'MedlineCitation.Article.Abstract.AbstractText',
		author:'"MedlineCitation"."Article"."AuthorList"."Author"',
		journal: '"MedlineCitation"."Article"."Journal"',
		meshHeadings: 'MedlineCitation.MeshHeadingList.MeshHeading',
		keywords:'"MedlineCitation"."KeywordList"."Keyword".list()'
	];

	static final Map<String, NodeType> NODE_TYPE_MAP = [
		pubmedId:NodeType.STRING,
		doi:NodeType.STRING,
		abs:NodeType.STRING,
		title: NodeType.STRING,
		keywords: NodeType.LIST
	];

	/**
	 * Constructor.
	 */
	public PubMedSlurper(){
		this.databaseName = DATABASE_NAME;
	}

	@Override
	public void parse(Article article, NodeChild node) {
		parseData(node, article, GPATH_MAP,NODE_TYPE_MAP);
		parseDate(node, "dateCreated", article)
		parseDate(node, "dateRevised", article)
		parseDate(node, "dateCompleted", article)
		parseAuthorList(node, article)
		parseJournal(node, article)
		parseKeywords(node, article)
		parseMeshHeadings(node, article)
	}

    /**
     * This method parses a date from an XML node, and assigns the result to
     * the specified article property.
     * @param node the XML node
     * @param propertyName the name of the property in the Article class to which the date will be assigned.
     * @param article the article to which the property belongs.
     */
	private void parseDate(NodeChild node, String propertyName, Article article){
		
		String year = parseString(node, GPATH_MAP.get(propertyName) + ".Year")
		String month = parseString(node, GPATH_MAP.get(propertyName) + ".Month")
		String day = parseString(node, GPATH_MAP.get(propertyName) + ".Day")
        String dateStr = null;
		try{
            dateStr = "${year}-${month}-${day}";
			if(dateStr != "--") {
				Date date = dateFormatter.parse(dateStr)
				article.setProperty(propertyName, date);
			}
		} catch(ParseException pe){
			log.debug(pe.getMessage(), pe);
            log.debug("DateStr: ${dateStr}")
		}
	}

    /**
     * This method parses a list of authors for a paper.
     * @param node the author list node
     * @param article the article to which the author belongs.
     */
	private void parseAuthorList(NodeChild node, Article article){
		
		NodeChildren nodeSet =  Eval.x(node, "x.${GPATH_MAP.author}" );
		
		nodeSet.each{ NodeChild child ->
			Author auth = new Author();
			auth.firstname = parseString(child, "ForeName");
			auth.lastname = parseString(child, "LastName");
			auth.initials = parseString(child, "Initials");

            if (auth.lastname != null) {
                article.authors.add(auth);
            }
		}
	}

    /**
     * This method parses the MeSH (Medical Search Headings) associated
     * with an article.
     * @param node the MeSH node
     * @param article the article to which the MeSH heading belongs.
     */
	private void parseMeshHeadings(NodeChild node, Article article){
		 NodeChildren children = Eval.x(node, "x.${GPATH_MAP.meshHeadings}")
		 children.each{ NodeChild child ->
			 MeshHeading mesh = new MeshHeading();
			 mesh.descriptorName = child.'DescriptorName';
			 
			 NodeChildren qualNodeSet = child.'QualifierName';
			 if(qualNodeSet != null){
				 qualNodeSet.each { NodeChild qualNode ->
					 mesh.qualifierNames.add(qualNode.text())
				 }
			 }
			 article.meshHeadings.add(mesh)
		 }
	}

    /**
     * This method parses a list of keywords associated with the article
     * @param node the node for the keyword list
     * @param article the article to which the keywords belong.
     */
	private void parseKeywords(NodeChild node, Article article){
		List<String> keywordList = parseList(node, GPATH_MAP.keywords);
		article.keywords.addAll(keywordList);
	}
	
	private void parseJournal(NodeChild node, Article article){
		GPathResult result = Eval.x(node, "x.${GPATH_MAP.journal}");
		NodeChild journalNode = result.getAt(0)

		Journal journal = new Journal();
		journal.title = parseString(journalNode, 'Title')
		journal.volume = parseString(journalNode, 'JournalIssue.Volume')
		journal.issue = parseString(journalNode, 'JournalIssue.Issue')
//		journal.publicationDate = new Date();
//		journal.publicationDate.year = parseInteger(journalNode, 'JournalIssue.PubDate.Year')
//		journal.publicationDate.month = parseInteger(journalNode, 'JournalIssue.PubDate.Month') ;
		journal.issn = parseString(journalNode, "ISSN")
		article.journal = journal;

	}
	
	

	@Override
	public Article read(InputStream inputStream) throws IOException {
		List<Article> articleList = readList(inputStream);
		return articleList?.get(0);
	}

	@Override
	public List<Article> readList(InputStream inputStream) throws IOException {
		List<Article> articleList = new ArrayList<>();
		
		XmlSlurper slurper = createSlurper()
		
		def root  = slurper.parse(inputStream)
		def nodeList = root.'PubmedArticle'

		nodeList.each{ NodeChild node ->
			Article article = new Article()
			parse(article, node)
			articleList.add(article);
		}
		root = null;
		return articleList;
		
	}



	@Override
	protected void parseDbReferences(NodeChild root, Article node) {
		// TODO implement method.
		
	}

    @Override
    public URL getUrl(String id, Map<String, String> paramMap){
		
		Map<String, String> map = [db:EUtilsURLFactory.DB_PUBMED, id:id, retmode:'xml']
		if(paramMap != null) {
			map.putAll(paramMap)
		}
		
        String url = EUtilsURLFactory.getURL( EUtilsURLFactory.EFETCH, map);
        return new URL(url);
    }

	@Override
	public Article fetch(String id) throws IOException {
        URL url = getUrl(id, null);
		log.debug("fetching: " + url);
		List<Article> articleList = readList(url.openStream());
		return articleList?.get(0);
	}

	@Override
	public List<Article> fetchAll(String id) throws IOException {
		URL url = getUrl(id, null);
		log.debug("fetching: " + url);
		List<Article> articleList = readList(url.openStream());
		return articleList;
	}

	@Override
	public IFetcher<Article> getNewInstance() {
		return new PubMedSlurper();
	}


}
